/* Copyright (C) 2010 Fredrique Samuels, fredriquesamuels@gmail.com 

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "3d_object.h"

namespace tri{

object_3d::object_3d() : tri::entity() { 
	mTexCBuffer = TriBuffer<float>(2);
}

object_3d::~object_3d() { 
}

void object_3d::render3DObject(TriBuffer<float>& VertexBuffer,
    							TriBuffer<float>& NormalBuffer,
    							TriBuffer<GLushort>& FaceBuffer,
    							    TriBuffer<float>& TexCBuffer,
    							    tri::Material& Material,
    							    std::string Texture){
	glEnable(GL_TEXTURE_2D);
	Material.apply();
	tri::applyTexture(Texture);
	glPushMatrix();
	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, VertexBuffer.getBuffer());
	if (NormalBuffer.size()>0) {
		glEnableClientState(GL_NORMAL_ARRAY);
		glNormalPointer(GL_FLOAT, 0, NormalBuffer.getBuffer());
	}
	
	if (TexCBuffer.size()>0) {
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(2,GL_FLOAT, 0, TexCBuffer.getBuffer());
	}
	
	glDrawElements(GL_TRIANGLES, FaceBuffer.size()*3, GL_UNSIGNED_SHORT, FaceBuffer.getBuffer());
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	
	/**
	 * Draw outline
	 */
	if (false){
		//draw outline
		glEnable (GL_BLEND);									// Enable Blending ( NEW )
		glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);		// Set The Blend Mode ( NEW )
		glPolygonMode (GL_BACK, GL_LINE);						// Draw Backfacing Polygons As Wireframes ( NEW )
		glLineWidth (3);								// Set The Line Width ( NEW )
		glCullFace (GL_FRONT);									// Don't Draw Any Front-Facing Polygons ( NEW )
		glDepthFunc (GL_LEQUAL);								// Change The Depth Mode ( NEW )
		glColor3f(1,0,0);
		
		glDrawElements(GL_TRIANGLES, FaceBuffer.size()*3, GL_UNSIGNED_SHORT, FaceBuffer.getBuffer());
		
		//undo settings
		glPolygonMode (GL_BACK, GL_FILL);
		glCullFace (GL_BACK);	
	}
	
	glDisableClientState(GL_VERTEX_ARRAY);
	glPopMatrix();
}

void object_3d::display(){
	render3DObject(mVertexBuffer,mNormalBuffer,mFaceBuffer,mTexCBuffer,
	    							    mMaterial,mTexture);	
}

void object_3d::calculateNormals(){
	if(mNormalBuffer.size()!=mVertexBuffer.size()){
		mNormalBuffer.allocate(mVertexBuffer.size());
	}
	if(mNormalBuffer.size()!=mVertexBuffer.size()){
		return;
	}
	for (int i=0;i<mNormalBuffer.size();i++){
		mNormalBuffer.assign(i,0,0,0);
	}

	for(int i = 0;i<mFaceBuffer.size();i++){
		tri::vector normal = getSurfaceNormal(i);
		GLushort* f = mFaceBuffer.get(i);
		updateNormal(f[0],normal);
		updateNormal(f[1],normal);
		updateNormal(f[2],normal);
	}
}

void object_3d::setTexture(std::string s){
	mTexture = s;
}

tri::vector object_3d::getSurfaceNormal(int faceIndex){
	GLushort* faces = mFaceBuffer.get(faceIndex);
	tri::vector v1(mVertexBuffer.get(faces[0]));
	tri::vector v2(mVertexBuffer.get(faces[1]));
	tri::vector v3(mVertexBuffer.get(faces[2]));
	tri::vector V = v2-v1; 
	tri::vector U = v3-v1;
	V.normalize();
	U.normalize();
	tri::vector n = V.crossProduct(&U); 
	n.normalize();
	return  n;
}

void object_3d::updateNormal(int index,tri::vector& n){
	tri::vector rn(mNormalBuffer.get(index));
	if ( rn == tri::vector(0,0,0) ){
		mNormalBuffer.assign(index,n.x,n.y,n.z);
	}else{
		rn+=n;
		rn/=2.0;
		rn.normalize();
		mNormalBuffer.assign(index,rn.x,rn.y,rn.z);
	}
}

std::map<std::string,Model3D::ObjectGroup*> Model3D::mModelList;

Model3D::ObjectGroup::ObjectGroup(){}

Model3D::ObjectGroup::~ObjectGroup(){
	for(int i =0;i<mObjectCount;i++){
		delete &mObjects[i];
	}
}

bool Model3D::loadModel(std::string key,std::string _file){
	
	if(_file == ""){
		_file = key;
	}
	if(!tri::sys::exists(_file)){
		tri::LOG_SEVERE("Unable to locate model file...:"+_file );
		return false;
	}
	
	int count = 0;
	tri::LOG_INFO("Loading Model from "+_file+" ..." );
	object_3d* objs = object_3d::load3dsMesh(count,_file.c_str());
	if (count == 0 || objs == NULL){
		tri::LOG_SEVERE("Unable to load model from file...:"+_file );
		return false;
	}
	
	if (Model3D::exists(key)){
		deleteModel(key);
	}
	
	mModelList[key] = new ObjectGroup();
	mModelList[key]->mObjectCount = count;
	mModelList[key]->mObjects = objs;
	for (int i = 0;i<mModelList[key]->mObjectCount;i++){
		mModelList[key]->mObjects[i].calculateNormals();
	}
	return true;
}

Model3D::Model3D():tri::entity(){
	
}

Model3D::~Model3D(){
	
}

void Model3D::display(){
	if (Model3D::exists(mModelGroup)){
		for (int i = 0;i<mModelList[mModelGroup]->mObjectCount;i++){
			mModelList[mModelGroup]->mObjects[i].setWireFrame(getWireFrame());
			mModelList[mModelGroup]->mObjects[i].render();
		}
	}
}

void Model3D::setModel(std::string model){
	this->mModelGroup = model;
}

object_3d* Model3D::getObject3D(std::string name){
	if (Model3D::exists(mModelGroup)){
		for (int i = 0;i<mModelList[mModelGroup]->mObjectCount;i++){
			std::cout<<mModelList[mModelGroup]->mObjects[i].getName()<<":"<< name<<std::endl;
			if(mModelList[mModelGroup]->mObjects[i].getName() == name){
				return &mModelList[mModelGroup]->mObjects[i];
			}
		}
	}
	
	return NULL;
}

void Model3D::getObjectList(std::vector<std::string>& list){
	list.clear();
	if (Model3D::exists(mModelGroup)){
		for (int i = 0;i<mModelList[mModelGroup]->mObjectCount;i++){
			list.push_back(mModelList[mModelGroup]->mObjects[i].getName());
		}
	}
}

void Model3D::deleteModel(std::string key){
	if (Model3D::exists(key)){
		Model3D::ObjectGroup* group = mModelList[key];
		delete group;
		mModelList.erase(key);
	}
}

bool Model3D::exists(std::string key){
	std::map<std::string,Model3D::ObjectGroup*>::iterator it;
	for ( it=mModelList.begin() ; it != mModelList.end(); it++ ){
		if ( (*it).first == key ){
			return true;
		}
	}
}

}